/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Map;

import java.io.FileNotFoundException;
import java.io.IOException;
import jcgame.Main;
import IO.Utils;
import Inrterface.Messages;
import Items.AddItems;
import Items.Item;
import Monsters.Monster;
import Monsters.Monsters;
import java.io.Serializable;
import java.util.ArrayList;
import net.slashie.libjcsi.CSIColor;

/**
 *
 * @author insane
 */
public class Map implements Serializable {

    private int width;
    private int height;
    public int x = 1;
    public int y = 1;
    private char[][] map = null;
    private int[][] color = null;
    private boolean[][] visible = null;
    private double density = 2.9;
    private int objHeight = 7;
    private int objWidth = 10;
    private int level;
    private CSIColor[][] seecolor = null;
    private char[][] seemap = null;
    private ArrayList walls;
    private int checkfull;
    private Map mp;
    private ArrayList<Item> items = null;
    private ArrayList<Monster> monster = null;
    private boolean[][] fovcolor;

    public Map(int level, int width, int height, double density, int objWidth, int objHeight) {
        this.density = density;
        this.width = width;
        this.height = height;
        this.level = level;
        this.objHeight = objHeight;
        this.objWidth = objWidth;
    }

    public void drawMap() throws FileNotFoundException, IOException, ClassNotFoundException {
        Utils ut = new Utils();
        //time check
        ut.timeCheck("start fov");
        //time check
        Fov fv = new Fov(map, color, visible, width, height, x, y);
        ArrayList results = fv.checkSee();
        color = (int[][]) results.get(0);
        visible = (boolean[][]) results.get(1);
        setFovcolor((boolean[][]) results.get(2));
        //time check
        ut.timeCheck("end fov");
        //time check

        preDraw();
        //time check
        ut.timeCheck("preDraw()");
        //time check
        postDraw(getFovcolor());
        //time check
        ut.timeCheck("timeCheck()");
        //time check
        paintMap();
        //time check
        ut.timeCheck("paintMap()");
        //time check
        //Messages.getInstance().add("hero : " + this.x + "-" + this.y);
    }

    public void paintMap() {
        for (int xx = 0; xx < MapConfig.getInstance().getViewSizeWidth() * 2; xx++) {
            for (int yy = 0; yy < MapConfig.getInstance().getViewSizeHeight() * 2; yy++) {
                print(xx, yy, seemap[xx][yy], seecolor[xx][yy]);
            }
        }
    }

    public void preDraw() {

        seecolor = new CSIColor[MapConfig.getInstance().getViewSizeWidth() * 2][MapConfig.getInstance().getViewSizeHeight() * 2];
        seemap = new char[MapConfig.getInstance().getViewSizeWidth() * 2][MapConfig.getInstance().getViewSizeHeight() * 2];

        int startx, starty;

        startx = x - MapConfig.getInstance().getViewSizeWidth();
        starty = y - MapConfig.getInstance().getViewSizeHeight();

        for (int xx = 0; xx < MapConfig.getInstance().getViewSizeWidth() * 2; xx++) {
            for (int yy = 0; yy < MapConfig.getInstance().getViewSizeHeight() * 2; yy++) {
                if (startx + xx < 0 || starty + yy < 0 || yy + starty > getHeight() - 1 || xx + startx > getWidth() - 1) {
                    save(xx, yy, '#', CSIColor.DARK_GRAY);
                } else {
                    if (visible[startx + xx][starty + yy]) {
                        save(xx, yy, map[startx + xx][starty + yy], MapUtils.getColor(color[startx + xx][starty + yy]));
                    } else {
                        save(xx, yy, map[startx + xx][starty + yy], MapUtils.getColor(0));
                    }
                    if (startx + xx == x && starty + yy == y) {
                        save(xx, yy, '@', CSIColor.GRAY);
                    }
                }

            }
        }
    }

    public void postDraw(boolean[][] fovcolor) {
        int xx = MapConfig.getInstance().getViewSizeWidth();
        int yy = MapConfig.getInstance().getViewSizeHeight();
        int fov = MapConfig.getInstance().getFov();
        int startx = xx - fov * 2;
        int starty = yy - fov;
        for (int w = 0; w <= fov * 4; w++) {
            for (int h = 0; h <= fov * 2; h++) {
                if (fovcolor[w][h]) {
                    save(startx + w, starty + h, seemap[startx + w][starty + h], new CSIColor().getColorFromCode(6));
                }
            }
        }
    }

    public void save(int x, int y, char text, CSIColor color) {
        this.seemap[x][y] = text;
        this.seecolor[x][y] = color;
    }

    public void save(int x, int y, char text, int realx, int realy) {
        this.seemap[x][y] = text;
        if (getVisible()[realx][realy]) {
            this.seecolor[x][y] = MapUtils.getColor(color[realx][realy]);
        } else {
            this.seecolor[x][y] = MapUtils.getColor(0);
        }
    }

    public void print(int x, int y, char text, CSIColor color) {
        Main.csi.print(x + MapConfig.getInstance().getPadding_left(), y + MapConfig.getInstance().getPadding_top(), text, color);
    }

    public void print(int x, int y, char text, int realx, int realy) {
        if (getVisible()[realx][realy]) {
            Main.csi.print(x + MapConfig.getInstance().getPadding_left(), y + MapConfig.getInstance().getPadding_top(), text, MapUtils.getColor(color[realx][realy]));
        } else {
            Main.csi.print(x + MapConfig.getInstance().getPadding_left(), y + MapConfig.getInstance().getPadding_top(), text, MapUtils.getColor(0));
        }
    }

    public char[][] createMap() throws FileNotFoundException, IOException, ClassNotFoundException {
        Utils ut = new Utils();
        //time check
        ut.timeCheck("paintMap()");
        //time check
        checkfull = 0;
        walls = new ArrayList();

        map = new char[width][height];
        color = new int[width][height];
        visible = new boolean[width][height];
        //time check
        ut.timeCheck("init()");
        //time check


        for (int a = 0; a < height; a++) {
            for (int b = 0; b < width; b++) {
                color[b][a] = 1;
                visible[b][a] = false;

            }
        }
        for (int h = 0; h < height; h++) {
            for (int w = 0; w < width; w++) {
                map[w][h] = '#';
                if (h == 0 || w == 0 || h == height - 1 || w == width - 1) {
                    color[w][h] = 2;
                }
            }
        }
        //center

        int roomHeight = Utils.rand(getObjHeight() - 5, getObjHeight());
        int roomWidth = Utils.rand(getObjWidth() - 5, getObjWidth());
        //center
        switch (Utils.rand(1, 5)) {
            case 1:
                y = 4 + roomHeight / 2;
                x = 4 + roomWidth / 2;
                break;
            case 2:
                y = height - 4 - roomHeight / 2;
                x = 4 + roomWidth / 2;
                break;
            case 3:
                y = height - 4 - roomHeight / 2;
                x = width - 4 - roomWidth / 2;
                break;
            case 4:
                y = height - 4 - roomHeight / 2;
                x = width - 4 - roomWidth / 2;
                break;
            case 5:
                x = width / 2;
                y = height / 2;
        }


        for (int a = 0; a < roomHeight; a++) {
            for (int b = 0; b < roomWidth; b++) {
                map[b + x - (int) Math.ceil((double) roomWidth / 2)][a + y - (int) Math.ceil((double) roomHeight / 2)] = '.';
                checkfull++;
                if (b == 0 || a == 0 || a == roomHeight - 1 || b == roomWidth - 1) {
                    addWall(b + x - (int) Math.ceil((double) roomWidth / 2), a + y - (int) Math.ceil((double) roomHeight / 2));
                }
            }
        }
        if (MapLevel.getInstance().getLevel() != 0) {
            map = new AddItems().addUpTransitions(this);
        }

        //time check
        ut.timeCheck("центральная комната");
        //time check
        this.mp = this;
        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    while (checkFull()) {
                        mp.createObj();
                    }

                    if (level != MapLevel.getInstance().getCountLevel() - 1) {
                        mp.map = new AddItems().addDownTransitions(mp);
                    }
                    if (MapLevel.getInstance().getLevel() != 0) {
                        mp.map = new AddItems().addItems(mp);
                    }
                } catch (Exception ex) {
                }
            }
        }).start();


        //time check
        ut.timeCheck("комнаты");
        //time check

        //time check
        ut.timeCheck("конец создания карты");
        //time check

        //добавление существ
        if (MapLevel.getInstance().getLevel() != 0) {
        int monstersCount = (this.width + this.height ) / 10;
        Monsters.add(this, monstersCount);
        }

        return map;
    }

    private void addWall(int x, int y) {
        int i[] = {x, y};
        walls.add(i);
    }

    private synchronized void createObj() {

        int i[] = selectWall();
        int roomHeight = Utils.rand(5, getObjHeight());
        int roomWidth = Utils.rand(5, getObjWidth());

        if (!checkRoom(i[0], i[1], roomWidth, roomHeight)) {
            createObj();
            return;
        } else {
            addRoom(i[0], i[1], roomWidth, roomHeight);
            return;
        }

    }

    private int[] selectWall() {
        int rand = Utils.rand(0, walls.size() - 1);
        int obj[] = (int[]) walls.get(rand);
        int checkx = obj[0];
        int checky = obj[1];
        int i[] = {checkx, checky};
        return i;
    }

    private boolean checkFull() {

        if (width * height / density < checkfull) {
            return false;
        } else {
            return true;
        }
    }

    private boolean checkIsExist(int x, int y) {
        if (x < 1 || y < 1 || y > getHeight() - 1 || x > getWidth() - 1 || color[x][y] == 2) {
            return false;
        } else {
            return true;
        }
    }

    private boolean checkRoom(int x, int y, int roomWidth, int roomHeight) {

        if (map[x + 1][y] == '#') {
            for (int yy = 0; yy < roomHeight; yy++) {
                for (int xx = 0; xx < roomWidth + 2; xx++) {
                    if (!checkIsExist(x + xx + 1, y + yy - (int) roomHeight / 2)) {
                        return false;
                    }
                    if (map[x + xx + 1][y + yy - (int) roomHeight / 2] == '.') {
                        return false;
                    }
                }
            }
        }

        if (map[x - 1][y] == '#') {
            for (int yy = 0; yy < roomHeight; yy++) {
                for (int xx = 0; xx < roomWidth + 2; xx++) {
                    if (!checkIsExist(x - xx - 1, y + yy - (int) roomHeight / 2)) {
                        return false;
                    }
                    if (map[x - xx - 1][y + yy - (int) roomHeight / 2] == '.') {
                        return false;
                    }
                }
            }

        }
        if (map[x][y + 1] == '#') {
            for (int yy = 0; yy < roomHeight + 2; yy++) {
                for (int xx = 0; xx < roomWidth; xx++) {
                    if (!checkIsExist(x + xx - (int) roomWidth / 2, y + yy + 1)) {
                        return false;
                    }
                    if (map[x + xx - (int) roomWidth / 2][y + yy + 1] == '.') {
                        return false;
                    }
                }
            }
        }
        if (map[x][y - 1] == '#') {
            for (int yy = 0; yy < roomHeight + 2; yy++) {
                for (int xx = 0; xx < roomWidth; xx++) {
                    if (!checkIsExist(x + xx - (int) roomWidth / 2, y - yy - 1)) {
                        return false;
                    }
                    if (map[x + xx - (int) roomWidth / 2][y - yy - 1] == '.') {
                        return false;
                    }
                }
            }
        }

        return true;
    }

    private void addDoor(int x, int y) {
        int check = Utils.rand(1, 5);
        if (check == 3) {
            map[x][y] = '.';
        } else {
            map[x][y] = '+';
        }
        checkfull++;
    }

    private void addRoom(int x, int y, int roomWidth, int roomHeight) {
        if (map[x + 1][y] == '#') {
            addDoor(x + 1, y);

            for (int yy = 0; yy < roomHeight; yy++) {
                for (int xx = 0; xx < roomWidth + 1; xx++) {
                    map[x + xx + 2][y + yy - (int) roomHeight / 2] = '.';
                    checkfull++;
                    if (yy == 0 || xx == 0 || yy < roomHeight - 1 || xx < roomWidth) {
                        addWall(x + xx + 2, y + yy - (int) roomHeight / 2);
                    }
                }
            }
            return;
        }

        if (map[x - 1][y] == '#') {
            addDoor(x - 1, y);
            for (int yy = 0; yy < roomHeight; yy++) {
                for (int xx = 0; xx < roomWidth + 1; xx++) {
                    map[x - xx - 2][y + yy - (int) roomHeight / 2] = '.';
                    checkfull++;
                    if (yy == 0 || xx == 0 || yy < roomHeight - 1 || xx < roomWidth) {
                        addWall(x - xx - 2, y + yy - (int) roomHeight / 2);
                    }
                }
            }
            return;
        }
        if (map[x][y + 1] == '#') {
            addDoor(x, y + 1);
            for (int yy = 0; yy < roomHeight + 1; yy++) {
                for (int xx = 0; xx < roomWidth; xx++) {
                    map[x + xx - (int) roomWidth / 2][y + yy + 2] = '.';
                    checkfull++;
                    if (yy == 0 || xx == 0 || yy < roomHeight || xx < roomWidth - 1) {
                        addWall(x + xx - (int) roomWidth / 2, y + yy + 2);
                    }
                }
            }
            return;
        }
        if (map[x][y - 1] == '#') {
            addDoor(x, y - 1);
            for (int yy = 0; yy < roomHeight + 1; yy++) {
                for (int xx = 0; xx < roomWidth; xx++) {
                    map[x + xx - (int) roomWidth / 2][y - yy - 2] = '.';
                    checkfull++;
                    if (yy == 0 || xx == 0 || yy < roomHeight || xx < roomWidth - 1) {
                        addWall(x + xx - (int) roomWidth / 2, y - yy - 2);
                    }
                }
            }
            return;
        }
    }

    /**
     * @return the map
     */
    public char[][] getMap() {
        return map;
    }

    /**
     * @param map the map to set
     */
    public void setMap(char[][] map) {
        this.map = map;
    }

    /**
     * @return the color
     */
    public int[][] getColor() {
        return color;
    }

    /**
     * @param color the color to set
     */
    public void setColor(int[][] color) {
        this.color = color;
    }

    /**
     * @return the visible
     */
    public boolean[][] getVisible() {
        return visible;
    }

    /**
     * @return the width
     */
    public int getWidth() {
        return width;
    }

    /**
     * @param width the width to set
     */
    public void setWidth(int width) {
        this.width = width;
    }

    /**
     * @return the height
     */
    public int getHeight() {
        return height;
    }

    /**
     * @param height the height to set
     */
    public void setHeight(int height) {
        this.height = height;
    }

    /**
     * @return the density
     */
    public double getDensity() {
        return density;
    }

    /**
     * @param density the density to set
     */
    public void setDensity(double density) {
        this.density = density;
    }

    /**
     * @return the objHeight
     */
    public int getObjHeight() {
        return objHeight;
    }

    /**
     * @param objHeight the objHeight to set
     */
    public void setObjHeight(int objHeight) {
        this.objHeight = objHeight;
    }

    /**
     * @return the objWidth
     */
    public int getObjWidth() {
        return objWidth;
    }

    /**
     * @param objWidth the objWidth to set
     */
    public void setObjWidth(int objWidth) {
        this.objWidth = objWidth;
    }

    /**
     * @return the level
     */
    public int getLevel() {
        return level;
    }

    /**
     * @param level the level to set
     */
    public void setLevel(int level) {
        this.level = level;
    }

    /**
     * @return the items
     */
    public ArrayList<Item> getItems() {
        if (items == null) {
            items = new ArrayList();
        }
        return items;
    }

    public Item getItem(int i) {
        if (items == null) {
            items = new ArrayList();
        }
        return items.get(i);
    }

    /**
     * @param items the items to set
     */
    public void setItems(ArrayList<Item> items) {
        this.items = items;
    }

    /**
     * @return the monster
     */
    public ArrayList<Monster> getMonster() {
        return monster;
    }

    /**
     * @param monster the monster to set
     */
    public void setMonster(ArrayList<Monster> monster) {
        this.monster = monster;
    }

    /**
     * @return the fovcolor
     */
    public boolean[][] getFovcolor() {
        return fovcolor;
    }

    /**
     * @param fovcolor the fovcolor to set
     */
    public void setFovcolor(boolean[][] fovcolor) {
        this.fovcolor = fovcolor;
    }
}
